#include "qelib.h"
#include "blktrace.h"



#define BLKTRACE_LOG_DOMAIN     "blktrace"
#define blktrace_debug(...)     qelog_debug(BLKTRACE_LOG_DOMAIN, __VA_ARGS__)
#define blktrace_info(...)      qelog_info(BLKTRACE_LOG_DOMAIN, __VA_ARGS__)
#define blktrace_notice(...)    qelog_notice(BLKTRACE_LOG_DOMAIN, __VA_ARGS__)
#define blktrace_warning(...)   qelog_warning(BLKTRACE_LOG_DOMAIN, __VA_ARGS__)
#define blktrace_critical(...)  qelog_critical(BLKTRACE_LOG_DOMAIN, __VA_ARGS__)
#define blktrace_error(...)     qelog_error(BLKTRACE_LOG_DOMAIN, __VA_ARGS__)
#define blktrace_fatal(...)	    qelog_fatal(BLKTRACE_LOG_DOMAIN, __VA_ARGS__)


#define START_MARK              "blktrace-start"
#define END_MARK                "sdtrace-end"
#define VERSION_MAJOR           (1)
#define VERSION_MINOR           (0)
#define VERSION_PATCH           (0)


static inline qe_u32 num_align(qe_u32 size, qe_u32 align)
{
    return ((size + align - 1) / align);
}

static inline qe_u32 chunk_to_block(blktrace_blockio_config *cfg, qe_u32 chunk)
{
    return chunk / cfg->chunks_per_block;
}

static void record_init(blktrace_record *rd)
{
    /* Strat mark ='blktrace-strat' */
    qe_sprintf(rd->start_mark, START_MARK);

    rd->version = qe_version_encode(VERSION_MAJOR,
        VERSION_MINOR, VERSION_PATCH);

    rd->size            = 0;
    rd->num_datas       = 0;
    rd->max_datas       = 0;
    rd->serialize_count = 0;
    rd->next_free       = 0;
    rd->is_full         = 0;
    rd->is_active       = 0;
    rd->data_start_mark = 0x44464F53;
    rd->data_end_mark   = 0x44464F45;

    /* End mark ='blktrace-end' */
    qe_sprintf(rd->end_mark, END_MARK);
}

static qe_ret meta_block_update(blktrace *trace)
{
    qe_ret ret;
    blktrace_blockio *bio = trace->bio;

    blktrace_debug("erase meta block");
    ret = bio->block_erase(bio, 0);
    if (ret != qe_ok) {
        blktrace_error("meta block erase error:%d", ret);
        return ret;
    }

    blktrace_debug("write meta chunk");
    ret = bio->chunk_write(bio, 0, &trace->record, trace->meta_size);
    if (ret != qe_ok) {
        blktrace_error("meta chunk write error:%d", ret);
        return ret;
    }

    return qe_ok;
}

static qe_ret block_record(blktrace *trace, qe_const_ptr data, qe_uint num)
{
    qe_u8 *pos;
    qe_int i;
    qe_ret ret;
    qe_u32 block;
    qe_u32 chunk;
    qe_u32 num_chunks;
    qe_u32 data_size;
    qe_u32 write_size;
    blktrace_blockio *bio = trace->bio;
    blktrace_blockio_config *cfg = &bio->config;
    blktrace_record *rd = &trace->record;

    pos   = (qe_u8 *)data;
    block = trace->record.next_free + cfg->start_block;
    chunk = block * cfg->chunks_per_block;

    /* erase blocks */
    for (i=0; i<trace->num_cache_blocks; i++) {
        blktrace_debug("erase block %d", block);
        ret = bio->block_erase(bio, block);
        if (ret != qe_ok) {
            blktrace_error("erase block %d error:%d", block, ret);
            return ret;
        }
        block++;
    }
    
    data_size = trace->data_size * num;

    /* write chunks */
    for (i=0; i<trace->num_cache_chunks; i++) {
        write_size = qe_min(data_size, cfg->chunk_size);
        blktrace_debug("write chunk %d %d", chunk, write_size);
        ret = bio->chunk_write(bio, chunk, pos, write_size);
        if (ret != qe_ok) {
            blktrace_error("write chunk %d error:%d", chunk, ret);
            return ret;
        }
        pos += write_size;
        data_size -= write_size;
        chunk++;
    }

    trace->record.num_datas += num;
    trace->record.next_free = (trace->record.next_free + trace->num_cache_blocks) % trace->num_data_blocks;
    blktrace_info("update next free %d", trace->record.next_free);

    /* check full */
    if (!trace->record.is_full) {
        if (trace->record.num_datas >= trace->record.max_datas) {
            trace->record.num_datas = trace->record.max_datas;
            trace->record.is_full = 1;
            blktrace_debug("record full");
        }
    }

    /* update meta block */
    return meta_block_update(trace);
}

/**
 * @brief Record data items
 * 
 * @param[in] trace: BlockTrace instance 
 * @param[in] data: items data pointer 
 * @param[in] num: items number 
 * @return qe_ret
 */
qe_ret blktrace_blockio_record(blktrace *trace, qe_const_ptr data, qe_uint num)
{
    qe_u8 *pos;
    qe_ret ret;
    qe_uint free;
    qe_uint num_push;
    blktrace_record *rd = &trace->record;

    if (!trace || !data || !num) {
        blktrace_error("invalid params");
        return qe_err_param;
    }

    if (!trace->is_initialized) {
        blktrace_error("not initialized");
        return qe_err_param;
    }

    if (!trace->bio || !trace->block_cache) {
        blktrace_error("no block io");
        return qe_err_param;
    }

    pos = (qe_u8 *)data;

    while (num) {

        /* push data into cache */
        free = qe_ringq_free(trace->block_cache);
        num_push = qe_min(free, num);
        qe_ringq_enq(trace->block_cache, pos, num_push);

        //blktrace_debug("cache push %d, wait %d", num_push, qe_ringq_wait(trace->block_cache));

        pos += trace->data_size * num_push;
        num -= num_push;

        /* cache full trigger block write */
        if (qe_ringq_isfull(trace->block_cache)) {
            blktrace_debug("cache full, trigger block record");
            ret = block_record(trace,
                qe_ringq_buf_pos(trace->block_cache), 
                qe_ringq_wait(trace->block_cache));
            if (ret != qe_ok) {
                blktrace_error("block record error:%d", ret);
                return ret;
            }
            /* don't forget to clear cache */
            qe_ringq_clear(trace->block_cache);
            blktrace_debug("block cache clear");
        }
    }

    return qe_ok;
}

/**
 * @brief Set BlockTracer block io
 * 
 * @param[in] trace: BlockTrace instance 
 * @param[in] bio: block io
 * @return qe_ret 
 */
qe_ret blktrace_set_blockio(blktrace *trace, blktrace_blockio *bio)
{
    qe_ret ret;
    qe_u32 block_size;
    qe_u32 num_data_blocks;
    qe_u32 num_max_datas;
    qe_u32 num_cache_datas;
    blktrace_record *rd = &trace->record;
    blktrace_blockio_config *cfg;

    if (!trace || !bio) {
        blktrace_error("invalid params");
        return qe_err_param;
    }

    if (!trace->is_initialized) {
        blktrace_error("not initialized");
        return qe_err_param;
    }

    if (!bio->block_erase || !bio->chunk_read || !bio->chunk_write || !bio->init) {
        blktrace_error("blockio error");
        return qe_err_param;
    }

    cfg = &bio->config;

    if (!cfg->chunk_size || !cfg->chunks_per_block || !cfg->num_cache_blocks) {
        blktrace_error("blockio config error");
        return qe_err_param;
    }

    if (cfg->end_block <= cfg->start_block) {
        blktrace_error("end block <= start block");
        return qe_err_param;
    }

    num_data_blocks = cfg->end_block - cfg->start_block;    // one for meta
    if (cfg->num_cache_blocks > num_data_blocks) {
        blktrace_error("cache blocks > data blocks");
        return qe_err_param;
    }

    /* Data blocks align to cache blocks */
    if (num_data_blocks % cfg->num_cache_blocks) {
        num_data_blocks = num_data_blocks / cfg->num_cache_blocks * cfg->num_cache_blocks;
        blktrace_debug("align data blocks to cache blocks %d", num_data_blocks);
    }

    /* call blockio init */
    ret = bio->init(bio);
    if (ret != qe_ok) {
        blktrace_error("blockio init error:%d", ret);
        return ret;
    }

    /* Alloc mem for cache blocks */
    block_size = cfg->chunk_size * cfg->chunks_per_block;
    blktrace_debug("block size %d", block_size);
    num_cache_datas = (cfg->num_cache_blocks * block_size) / trace->data_size;
    blktrace_debug("cache blocks:%d num datas:%d", cfg->num_cache_blocks, num_cache_datas);
    if (!num_cache_datas) {
        qe_error("data size and block size not match");
        return qe_err_param;
    }

    trace->block_cache = qe_ringq_create(trace->data_size, num_cache_datas);
    if (!trace->block_cache) {
        qe_error("alloc mem for cache blocks failed");
        return qe_err_mem;
    }
    blktrace_debug("create block cache success, cache datas %d", num_cache_datas);

    trace->bio = bio;
    trace->num_cache_blocks = cfg->num_cache_blocks;
    trace->num_cache_chunks = cfg->num_cache_blocks * cfg->chunks_per_block;
    trace->num_data_blocks  = num_data_blocks;

    /* update record */
    rd->max_datas = num_cache_datas * num_data_blocks / cfg->num_cache_blocks;
    rd->size = (num_data_blocks + 1) * block_size;
    rd->next_free = 1;
    rd->is_active = 1;

    blktrace_debug("data blocks:%d max datas:%d total size:%d", num_data_blocks, 
        rd->max_datas, rd->size);

    /* update meta block */
    ret = meta_block_update(trace);
    if (ret != qe_ok) {
        blktrace_error("meta block update error:%d", ret);
        return ret;
    }

    blktrace_info("blockio set success");
    blktrace_info("chunk size    : %d", cfg->chunk_size);
    blktrace_info("block size    : %d", block_size);
    blktrace_info("config blocks : %d", cfg->end_block - cfg->start_block + 1);
    blktrace_info("used blocks   : %d + %d", 1, num_data_blocks);
    blktrace_info("unused blocks : %d", cfg->end_block - cfg->start_block - num_data_blocks);
    blktrace_info("cache blocks  : %d", cfg->num_cache_blocks);
    blktrace_info("cache datas   : %d", num_cache_datas);
    blktrace_info("cache size    : %d", trace->data_size * num_cache_datas);
    blktrace_info("max datas     : %d", rd->max_datas);
    blktrace_info("total size    : %d", rd->size);

    return qe_ok;
}

/**
 * @brief Create a block trace
 * 
 * @param[in] size: item size in bytes 
 * @return blktrace* 
 */
blktrace *blktrace_new(qe_uint size)
{
    blktrace *trace;

    if (!size) {
        blktrace_error("invalid params");
        return QE_NULL;
    }

    trace = qe_malloc(sizeof(blktrace));
    if (!trace) {
        blktrace_error("alloc mem for trace failed");
        return QE_NULL;
    }
    qe_memset(trace, 0, sizeof(blktrace));

    record_init(&trace->record);

    trace->bio            = QE_NULL;
    trace->data_size      = size;
    trace->is_initialized = qe_true;

    blktrace_debug("create trace success");

    return trace;
}